﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml;

namespace Aeon.Configuration
{
    /// <summary>
    /// Abstract base class for Aeon configurations.
    /// </summary>
    public abstract class AeonConfiguration : IAeonConfiguration
    {
        #region Private Fields
        /// <summary>
        /// The current error message.
        /// </summary>
        private string currentError = string.Empty;
        /// <summary>
        /// The dictionary of property error messages.
        /// </summary>
        private readonly Dictionary<string, string> errors = new Dictionary<string, string>();
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the AeonConfiguration class.
        /// </summary>
        protected AeonConfiguration()
        {
        }
        #endregion

        #region Public Events
        /// <summary>
        /// Occurs when a property value has changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets a value indicating whether there are any errors in this configuration.
        /// </summary>
        public bool HasErrors
        {
            get { return !string.IsNullOrEmpty(this.currentError); }
        }
        /// <summary>
        /// Gets the current error message for this configuration.
        /// </summary>
        string IDataErrorInfo.Error
        {
            get { return this.currentError; }
        }
        #endregion

        #region Private Properties
        /// <summary>
        /// Sets the current error message for this configuration.
        /// </summary>
        private string Error
        {
            set
            {
                if(value != this.currentError)
                {
                    bool hasErrorsChanged = string.IsNullOrEmpty(value) || string.IsNullOrEmpty(this.currentError);
                    this.currentError = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("Error"));
                    if(hasErrorsChanged)
                        OnPropertyChanged(new PropertyChangedEventArgs("HasErrors"));
                }
            }
        }
        #endregion

        #region Public Indexers
        /// <summary>
        /// Gets the current error message for a specific property.
        /// </summary>
        /// <param name="columnName">Name of the property.</param>
        /// <returns>Error message for the property.</returns>
        string IDataErrorInfo.this[string columnName]
        {
            get
            {
                string error;
                if(this.errors.TryGetValue(columnName, out error))
                    return error ?? string.Empty;
                else
                    return string.Empty;
            }
        }
        #endregion

        /// <summary>
        /// Serializes the configuration to an XML element.
        /// </summary>
        /// <param name="xmlWriter">XmlWriter used to serialize the object.</param>
        void IAeonConfiguration.Serialize(XmlWriter xmlWriter)
        {
            if(xmlWriter == null)
                throw new ArgumentNullException("xmlWriter");

            Serialize(xmlWriter);
        }
        /// <summary>
        /// Deserializes the configuration from an XML element.
        /// </summary>
        /// <param name="xmlReader">XmlReader used to deserialize the object.</param>
        void IAeonConfiguration.Deserialize(XmlReader xmlReader)
        {
            if(xmlReader == null)
                throw new ArgumentNullException("xmlReader");

            Deserialize(xmlReader);
        }

        #region Protected Methods
        /// <summary>
        /// Raises the PropertyChanged event.
        /// </summary>
        /// <param name="e">Information about the event.</param>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var handler = this.PropertyChanged;
            if(handler != null)
                handler(this, e);
        }
        /// <summary>
        /// Sets the error message for a property.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="errorMessage">Error message for the property.</param>
        protected void SetPropertyError(string propertyName, string errorMessage)
        {
            if(propertyName == null)
                throw new ArgumentNullException("propertyName");

            if(!string.IsNullOrEmpty(errorMessage))
                this.errors[propertyName] = errorMessage;
            else
                this.errors.Remove(propertyName);

            if(this.errors.Count == 0)
                this.Error = string.Empty;
            else
                this.Error = this.errors.Values.First();
        }
        /// <summary>
        /// Clears the error message for a property.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        protected void ClearPropertyError(string propertyName)
        {
            if(propertyName == null)
                throw new ArgumentNullException("propertyName");

            SetPropertyError(propertyName, string.Empty);
        }
        /// <summary>
        /// Serializes the configuration to an XML element.
        /// </summary>
        /// <param name="xmlWriter">XmlWriter used to serialize the object.</param>
        protected abstract void Serialize(XmlWriter xmlWriter);
        /// <summary>
        /// Deserializes the configuration from an XML element.
        /// </summary>
        /// <param name="xmlReader">XmlReader used to deserialize the object.</param>
        protected abstract void Deserialize(XmlReader xmlReader);
        #endregion
    }
}
